package com.cdream.basic.java8.io.niochat;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Set;

/**
 * @author cdream
 * @date 2022/8/16
 */
@Slf4j
public class ChatServer {
    private static final Integer PORT = 18080;
    private static final String HOST = "localhost";
    private static final String QUIT = "QUIT";
    private static final int BUFFER = 1024;
    private static Selector selector = null;
    private static ByteBuffer rBuffer = null;
    private static ByteBuffer wBuffer = null;

    public static void main(String[] args) {
        rBuffer = ByteBuffer.allocate(BUFFER);
        wBuffer = ByteBuffer.allocate(BUFFER);

        ServerSocketChannel ssc = null;


        try {
            ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            // todo 待处理
            ssc.bind(new InetSocketAddress(PORT));

            selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);


            while (true) {
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                for (SelectionKey key : keys) {
                    handles(key);
                }
                keys.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IoUtil.close(selector);
        }

    }

    private static void handles(SelectionKey key) throws IOException {
        // ACCEPT 事件 和客户端建立连接
        if (key.isAcceptable()) {
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
            log.info("客户端{}已连接", client.socket().getPort());
        }
        // READ 事件 客户端发送消息
        if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
            String msg = receive(client);
            log.info("收到客户端:{},消息:{}",client.socket().getPort(),msg);
            if (StrUtil.isEmpty(msg)) {
                key.cancel();
                selector.wakeup();
            } else {
                forwardMsg(client, msg);
                if (isQuit(msg)) {
                    key.cancel();
                    selector.wakeup();
                    log.info("客户端{}已断开连接", client.socket().getPort());
                }
            }
        }
    }

    private static void forwardMsg(SocketChannel client, String msg) throws IOException {
        for (SelectionKey key : selector.keys()) {
            if (key.channel() instanceof ServerSocketChannel){
                continue;
            }
            SocketChannel channel = (SocketChannel) key.channel();

            if (key.isValid() && !client.equals(channel)){
                wBuffer.clear();
                wBuffer.put(StandardCharsets.UTF_8.encode(msg));
                wBuffer.flip();
                while (wBuffer.hasRemaining()){
                    channel.write(wBuffer);
                }
            }
        }
    }

    private static String receive(SocketChannel client) throws IOException {
        rBuffer.clear();
        while (client.read(rBuffer)>0);
        rBuffer.flip();
        return String.valueOf(StandardCharsets.UTF_8.decode(rBuffer));
    }

    public static boolean isQuit(String msg) {
        return "QUIT".equals(msg);
    }


}
